package com.googlecode.lazyrecords.berkeleydb;

import com.googlecode.lazyrecords.Definition;
import com.googlecode.lazyrecords.Keyword;
import com.googlecode.lazyrecords.Keywords;
import com.googlecode.lazyrecords.Record;
import com.googlecode.lazyrecords.mappings.StringMappings;
import com.googlecode.totallylazy.Function1;
import com.googlecode.totallylazy.Pair;
import com.googlecode.totallylazy.Sequence;
import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.bind.tuple.TupleInput;
import com.sleepycat.bind.tuple.TupleOutput;
import com.sleepycat.je.DatabaseEntry;

import static com.googlecode.lazyrecords.Definition.methods.sortFields;
import static com.googlecode.totallylazy.Callables.second;
import static com.googlecode.totallylazy.LazyException.lazyException;
import static com.googlecode.totallylazy.Predicates.is;
import static com.googlecode.totallylazy.Predicates.notNullValue;
import static com.googlecode.totallylazy.Predicates.where;

public class RecordTuple extends TupleBinding<Record> {
    public static final StringMappings MAPPINGS = new StringMappings();

    static Function1<Record, DatabaseEntry> toEntry(final Definition definition, final RecordTuple recordTuple) {
        return new Function1<Record, DatabaseEntry>() {
            @Override
            public DatabaseEntry call(final Record record) throws Exception {
                return recordTuple.objectToEntry(sortFields(definition, record));
            }
        };
    }

    @Override
    public Record entryToObject(TupleInput input) {
        try {
            Record record = Record.constructors.record();
            int numberOfFields = input.readInt();
            for (int i = 0; i < numberOfFields; i++) {
                String name = input.readString();
                Class<?> type = Class.forName(input.readString());
                Object value = MAPPINGS.get(type).toValue(input.readString());
                Keyword<Object> keyword = Keywords.keyword(name, type);
                record = record.set(keyword, value);
            }
            return record;
        } catch (Exception e) {
            throw lazyException(e);
        }

    }

    public DatabaseEntry objectToEntry(Record record) {
        DatabaseEntry value = new DatabaseEntry();
        objectToEntry(record, value);
        return value;
    }

    @Override
    public void objectToEntry(Record record, TupleOutput output) {
        try {
            Sequence<Pair<Keyword<?>, Object>> fields = record.fields().filter(where(second(), is(notNullValue())));
            output.writeInt(fields.size());
            for (Pair<Keyword<?>, Object> pair : fields) {
                Keyword keyword = pair.first();
                Object value = pair.second();
                Class<?> aClass = keyword.forClass();
                output.writeString(keyword.name());
                output.writeString(aClass.getName());
                output.writeString(MAPPINGS.get(aClass).toString(value));
            }
        } catch (Exception e) {
            throw new UnsupportedOperationException(e);
        }
    }
}
